// SPDX-License-Identifier: GPL-2.0
/*
 * OV8865 MIPI Camera Subdev Driver
 * Copyright (C) 2020 Kévin L'hôpital.
 * Based on the ov5640 driver and an out of tree ov8865 driver by Allwinner.
 */

#include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>

#define OV8865_ACPI_HID "INT347A"

#define OV8865_XCLK_FREQ		24000000

/* System */

#define OV8865_SW_STANDBY_REG		0x0100
#define OV8865_SW_STANDBY_STANDBY_N	BIT(0)

#define OV8865_SW_RESET_REG		0x0103

#define OV8865_PLL_CTRL2_REG		0x0302
#define OV8865_PLL_CTRL3_REG		0x0303
#define OV8865_PLL_CTRL4_REG		0x0304
#define OV8865_PLL_CTRLE_REG		0x030e
#define OV8865_PLL_CTRLF_REG		0x030f
#define OV8865_PLL_CTRL12_REG		0x0312
#define OV8865_PLL_CTRL1E_REG		0x031e

#define OV8865_SLAVE_ID_REG		0x3004
#define OV8865_SLAVE_ID_DEFAULT		0x36

#define OV8865_PUMP_CLK_DIV_REG		0x3015

#define OV8865_MIPI_CTRL_REG		0x3018
#define OV8865_CLOCK_SEL_REG		0x3020
#define OV8865_MIPI_SC_CTRL_REG		0X3022

#define OV8865_CHIP_ID_REG		0x300a
#define OV8865_CHIP_ID			0x008865

/* Exposure/gain/banding */

#define OV8865_EXPOSURE_CTRL_HH_REG	0x3500
#define OV8865_EXPOSURE_CTRL_H_REG	0x3501
#define OV8865_EXPOSURE_CTRL_L_REG	0x3502
#define OV8865_MANUAL_CTRL_REG		0x3503
#define OV8865_GAIN_CTRL_H_REG		0x3508
#define OV8865_GAIN_CTRL_L_REG		0x3509

#define OV8865_ASP_CTRL41_REG		0x3641
#define OV8865_ASP_CTRL46_REG		0x3646
#define OV8865_ASP_CTRL47_REG		0x3647
#define OV8865_ASP_CTRL50_REG		0x364a

/* Timing control */
#define OV8865_X_ADDR_START_H_REG	0x3800
#define OV8865_X_ADDR_START_L_REG	0x3801
#define OV8865_Y_ADDR_START_H_REG	0x3802
#define OV8865_Y_ADDR_START_L_REG	0x3803
#define OV8865_X_ADDR_END_H_REG		0x3804
#define OV8865_X_ADDR_END_L_REG		0x3805
#define OV8865_Y_ADDR_END_H_REG		0x3806
#define OV8865_Y_ADDR_END_L_REG		0x3807
#define OV8865_X_OUTPUT_SIZE_REG	0x3808
#define OV8865_Y_OUTPUT_SIZE_REG	0x380a
#define OV8865_HTS_REG			0x380c
#define OV8865_VTS_REG			0x380e
#define OV8865_ISP_X_WIN_H_REG		0x3810
#define OV8865_ISP_X_WIN_L_REG		0x3811
#define OV8865_ISP_Y_WIN_L_REG		0x3813
#define OV8865_X_INC_ODD_REG		0x3814
#define OV8865_X_INC_EVEN_REG		0x3815
#define OV8865_FORMAT1_REG		0x3820
#define OV8865_FORMAT1_MIRROR_ARR	BIT(1)
#define OV8865_FORMAT1_MIRROR_DIG	BIT(2)
#define OV8865_FORMAT2_REG		0x3821
#define OV8865_FORMAT2_MIRROR_ARR	BIT(1)
#define OV8865_FORMAT2_MIRROR_DIG	BIT(2)
#define OV8865_Y_INC_ODD_REG		0x382a
#define OV8865_Y_INC_EVEN_REG		0x382b
#define OV8865_BLC_NUM_OPTION_REG	0x3830
#define OV8865_ZLINE_NUM_OPTION_REG	0x3836
#define OV8865_RGBC_REG			0x3837
#define OV8865_AUTO_SIZE_CTRL0_REG	0x3841
#define OV8865_BOUNDARY_PIX_NUM_REG	0x3846

/* OTP */

#define OV8865_OTP_REG			0x3d85
#define OV8865_OTP_SETT_STT_ADDR_H_REG	0x3d8c
#define OV8865_OTP_SETT_STT_ADDR_L_REG	0x3d8d

/* Black Level */

#define OV8865_BLC_CTRL0_REG		0x4000
#define OV8865_BLC_CTRL1_REG		0x4001
#define OV8865_BLC_CTRL5_REG		0x4005
#define OV8865_BLC_CTRLB_REG            0x400b
#define OV8865_BLC_CTRLD_REG            0x400d
#define OV8865_BLC_CTRL1B_REG           0x401b
#define OV8865_BLC_CTRL1D_REG           0x401d
#define OV8865_BLC_CTRL1F_REG		0x401f
#define OV8865_ANCHOR_LEFT_START_H_REG	0x4020
#define OV8865_ANCHOR_LEFT_START_L_REG	0x4021
#define OV8865_ANCHOR_LEFT_END_H_REG	0x4022
#define OV8865_ANCHOR_LEFT_END_L_REG	0x4023
#define OV8865_ANCHOR_RIGHT_START_H_REG	0x4024
#define OV8865_ANCHOR_RIGHT_START_L_REG	0x4025
#define OV8865_ANCHOR_RIGHT_END_H_REG	0x4026
#define OV8865_ANCHOR_RIGHT_END_L_REG	0x4027
#define OV8865_TOP_ZLINE_ST_REG		0x4028
#define OV8865_TOP_ZLINE_NUM_REG	0x4029
#define OV8865_TOP_BKLINE_ST_REG	0x402a
#define OV8865_TOP_BKLINE_NUM_REG	0x402b
#define OV8865_BOT_ZLINE_ST_REG		0x402c
#define OV8865_BOT_ZLINE_NUM_REG	0x402d
#define OV8865_BOT_BLKLINE_ST_REG	0x402e
#define OV8865_BOT_BLKLINE_NUM_REG	0x402f
#define OV8865_BLC_OFFSET_LIMIT_REG	0x4034

/* Format Control */

#define OV8865_CLIP_MAX_HI_REG		0x4300
#define OV8865_CLIP_MIN_HI_REG		0x4301
#define OV8865_CLIP_LO_REG		0x4302

#define OV8865_R_VFIFO_READ_START_REG	0x4601

/* MIPI Control */

#define OV8865_MIPI_CTRL13_REG		0x4813
#define OV8865_CLK_PREPARE_MIN_REG	0x481f
#define OV8865_PCLK_PERIOD_REG		0x4837
#define OV8865_LANE_SEL01_REG		0x4850
#define OV8865_LANE_SEL23_REG		0x4851

/* LVDS Control */

#define OV8865_LVDS_R0_REG		0x4b00
#define OV8865_LVDS_BLK_TIMES_H_REG	0x4b0c
#define OV8865_LVDS_BLK_TIMES_L_REG	0x4b0d

/* DSP Control */

#define OV8865_ISP_CTRL0_REG		0x5000
#define OV8865_ISP_CTRL1_REG		0x5001
#define OV8865_ISP_CTRL2_REG		0x5002

#define OV8865_AVG_READOUT_REG		0x568a

/* Pre DSP Control */

#define OV8865_PRE_CTRL0		0x5e00
#define OV8865_PRE_CTRL1		0x5e01

/* OTP DPC Control */

#define OV8865_OTP_CTRL0		0x5b00
#define OV8865_OTP_CTRL1		0x5b01
#define OV8865_OTP_CTRL2		0x5b02
#define OV8865_OTP_CTRL3		0x5b03
#define OV8865_OTP_CTRL5		0x5b05

/* LENC Control */

#define OV8865_LENC_G0_REG		0x5800
#define OV8865_LENC_G1_REG		0x5801
#define OV8865_LENC_G2_REG		0x5802
#define OV8865_LENC_G3_REG		0x5803
#define OV8865_LENC_G4_REG		0x5804
#define OV8865_LENC_G5_REG		0x5805
#define OV8865_LENC_G10_REG		0x5806
#define OV8865_LENC_G11_REG		0x5807
#define OV8865_LENC_G12_REG		0x5808
#define OV8865_LENC_G13_REG		0x5809
#define OV8865_LENC_G14_REG		0x580a
#define OV8865_LENC_G15_REG		0x580b
#define OV8865_LENC_G20_REG		0x580c
#define OV8865_LENC_G21_REG		0x580d
#define OV8865_LENC_G22_REG		0x580e
#define OV8865_LENC_G23_REG		0x580f
#define OV8865_LENC_G24_REG		0x5810
#define OV8865_LENC_G25_REG		0x5811
#define OV8865_LENC_G30_REG		0x5812
#define OV8865_LENC_G31_REG		0x5813
#define OV8865_LENC_G32_REG		0x5814
#define OV8865_LENC_G33_REG		0x5815
#define OV8865_LENC_G34_REG		0x5816
#define OV8865_LENC_G35_REG		0x5817
#define OV8865_LENC_G40_REG		0x5818
#define OV8865_LENC_G41_REG		0x5819
#define OV8865_LENC_G42_REG		0x581a
#define OV8865_LENC_G43_REG		0x581b
#define OV8865_LENC_G44_REG		0x581c
#define OV8865_LENC_G45_REG		0x581d
#define OV8865_LENC_G50_REG		0x581e
#define OV8865_LENC_G51_REG		0x581f
#define OV8865_LENC_G52_REG		0x5820
#define OV8865_LENC_G53_REG		0x5821
#define OV8865_LENC_G54_REG		0x5822
#define OV8865_LENC_G55_REG		0x5823
#define OV8865_LENC_BR0_REG		0x5824
#define OV8865_LENC_BR1_REG		0x5825
#define OV8865_LENC_BR2_REG		0x5826
#define OV8865_LENC_BR3_REG		0x5827
#define OV8865_LENC_BR4_REG		0x5828
#define OV8865_LENC_BR10_REG		0x5829
#define OV8865_LENC_BR11_REG		0x582a
#define OV8865_LENC_BR12_REG		0x582b
#define OV8865_LENC_BR13_REG		0x582c
#define OV8865_LENC_BR14_REG		0x582d
#define OV8865_LENC_BR20_REG		0x582e
#define OV8865_LENC_BR21_REG		0x582f
#define OV8865_LENC_BR22_REG		0x5830
#define OV8865_LENC_BR23_REG		0x5831
#define OV8865_LENC_BR24_REG		0x5832
#define OV8865_LENC_BR30_REG		0x5833
#define OV8865_LENC_BR31_REG		0x5834
#define OV8865_LENC_BR32_REG		0x5835
#define OV8865_LENC_BR33_REG		0x5836
#define OV8865_LENC_BR34_REG		0x5837
#define OV8865_LENC_BR40_REG		0x5838
#define OV8865_LENC_BR41_REG		0x5839
#define OV8865_LENC_BR42_REG		0x583a
#define OV8865_LENC_BR43_REG		0x583b
#define OV8865_LENC_BR44_REG		0x583c
#define OV8865_LENC_BROFFSET_REG	0x583d

enum ov8865_mode_id {
	OV8865_MODE_QUXGA_3264_2448 = 0,
	OV8865_MODE_6M_3264_1836,
	OV8865_MODE_1080P_1920_1080,
	OV8865_MODE_720P_1280_720,
	OV8865_MODE_UXGA_1600_1200,
	OV8865_MODE_SVGA_800_600,
	OV8865_MODE_VGA_640_480,
	OV8865_NUM_MODES,
};


enum ov8865_frame_rate {
	OV8865_30_FPS = 0,
	OV8865_90_FPS,
	OV8865_NUM_FRAMERATES,
};

static const int ov8865_framerates[] = {
	[OV8865_30_FPS] = 30,
	[OV8865_90_FPS] = 90,
};

struct ov8865_pixfmt {
	u32 code;
	u32 colorspace;
};

static const struct ov8865_pixfmt ov8865_formats[] = {
	{ MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_COLORSPACE_RAW, },
};

/* regulator supplies */
static const char * const ov8865_supply_names[] = {
	"AVDD",  /* Analog (2.8V) supply */
	"DOVDD", /* Digital I/O (1,8V/2.8V) supply */
	"VDD2",  /* Digital Core (1.2V) supply */
	"AFVDD",
};

#define OV8865_NUM_SUPPLIES ARRAY_SIZE(ov8865_supply_names)

struct reg_value {
	u16 reg_addr;
	u8 val;
	u32 delay_ms;
};

#define OV8865_LINK_FREQ_422MHZ			422400000

static const s64 link_freq_menu_items[] = {
	OV8865_LINK_FREQ_422MHZ
};

struct ov8865_mode_info {
	enum ov8865_mode_id id;
	u32 hact;
	u32 htot;
	u32 vact;
	u32 vtot;
	const struct reg_value *reg_data;
	u32 reg_data_size;
};

struct ov8865_ctrls {
	struct v4l2_ctrl_handler handler;
	struct v4l2_ctrl *pixel_rate;
	struct v4l2_ctrl *exposure;
	struct v4l2_ctrl *gain;
	struct v4l2_ctrl *hflip;
	struct v4l2_ctrl *vflip;
	struct v4l2_ctrl *link_freq;
};

struct ov8865_dev {
	struct i2c_client *i2c_client;
	struct v4l2_subdev sd;
	struct media_pad pad;
	struct v4l2_fwnode_endpoint ep;
	struct clk *xclk;

	/* For DT-based systems */
	struct regulator_bulk_data supplies[OV8865_NUM_SUPPLIES];
	struct gpio_desc *reset_gpio;
	struct gpio_desc *pwdn_gpio;

	bool upside_down;

	struct mutex lock;

	int power_count;

	struct v4l2_mbus_framefmt fmt;

	const struct ov8865_mode_info *current_mode;
	const struct ov8865_mode_info *last_mode;
	enum ov8865_frame_rate current_fr;
	struct v4l2_fract frame_interval;
	struct ov8865_ctrls ctrls;

	bool streaming;

	/* dependent device (PMIC) */
	struct device *dep_dev;

	/* GPIOs defined in dep_dev _CRS */
	struct gpio_descs *dep_gpios;

	bool is_acpi_based;
};

static inline struct ov8865_dev *to_ov8865_dev(struct v4l2_subdev *sd)
{
	return container_of(sd, struct ov8865_dev, sd);
}

static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
{
	return &container_of(ctrl->handler, struct ov8865_dev,
			     ctrls.handler)->sd;
}

static const struct reg_value ov8865_init_setting_QUXGA[] = {
	{ OV8865_SW_RESET_REG, 0x01, 16 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
	{ 0x3638, 0xff },
	{ OV8865_PUMP_CLK_DIV_REG, 0x01 },
	{ OV8865_MIPI_SC_CTRL_REG, 0x01 },
	{ 0x3031, 0x0a },
	{ 0x3305, 0xf1 },
	{ 0x3308, 0x00 },
	{ 0x3309, 0x28 },
	{ 0x330a, 0x00 },
	{ 0x330b, 0x20 },
	{ 0x330c, 0x00 },
	{ 0x330d, 0x00 },
	{ 0x330e, 0x00 },
	{ 0x330f, 0x40 },
	{ 0x3307, 0x04 },
	{ 0x3604, 0x04 },
	{ 0x3602, 0x30 },
	{ 0x3605, 0x00 },
	{ 0x3607, 0x20 },
	{ 0x3608, 0x11 },
	{ 0x3609, 0x68 },
	{ 0x360a, 0x40 },
	{ 0x360c, 0xdd },
	{ 0x360e, 0x0c },
	{ 0x3610, 0x07 },
	{ 0x3612, 0x86 },
	{ 0x3613, 0x58 },
	{ 0x3614, 0x28 },
	{ 0x3617, 0x40 },
	{ 0x3618, 0x5a },
	{ 0x3619, 0x9b },
	{ 0x361c, 0x00 },
	{ 0x361d, 0x60 },
	{ 0x3631, 0x60 },
	{ 0x3633, 0x10 },
	{ 0x3634, 0x10 },
	{ 0x3635, 0x10 },
	{ 0x3636, 0x10 },
	{ OV8865_ASP_CTRL41_REG, 0x55 },
	{ OV8865_ASP_CTRL46_REG, 0x86 },
	{ OV8865_ASP_CTRL47_REG, 0x27 },
	{ OV8865_ASP_CTRL50_REG, 0x1b },
	{ OV8865_EXPOSURE_CTRL_HH_REG, 0x00 },
	{ OV8865_EXPOSURE_CTRL_H_REG, 0x4c },
	{ OV8865_EXPOSURE_CTRL_L_REG, 0x00 },
	{ OV8865_MANUAL_CTRL_REG, 0x00 },
	{ OV8865_GAIN_CTRL_H_REG, 0x02 },
	{ OV8865_GAIN_CTRL_L_REG, 0x00 },
	{ 0x3700, 0x24 },
	{ 0x3701, 0x0c },
	{ 0x3702, 0x28 },
	{ 0x3703, 0x19 },
	{ 0x3704, 0x14 },
	{ 0x3705, 0x00 },
	{ 0x3706, 0x38 },
	{ 0x3707, 0x04 },
	{ 0x3708, 0x24 },
	{ 0x3709, 0x40 },
	{ 0x370a, 0x00 },
	{ 0x370b, 0xb8 },
	{ 0x370c, 0x04 },
	{ 0x3718, 0x12 },
	{ 0x3719, 0x31 },
	{ 0x3712, 0x42 },
	{ 0x3714, 0x12 },
	{ 0x371e, 0x19 },
	{ 0x371f, 0x40 },
	{ 0x3720, 0x05 },
	{ 0x3721, 0x05 },
	{ 0x3724, 0x02 },
	{ 0x3725, 0x02 },
	{ 0x3726, 0x06 },
	{ 0x3728, 0x05 },
	{ 0x3729, 0x02 },
	{ 0x372a, 0x03 },
	{ 0x372b, 0x53 },
	{ 0x372c, 0xa3 },
	{ 0x372d, 0x53 },
	{ 0x372e, 0x06 },
	{ 0x372f, 0x10 },
	{ 0x3730, 0x01 },
	{ 0x3731, 0x06 },
	{ 0x3732, 0x14 },
	{ 0x3733, 0x10 },
	{ 0x3734, 0x40 },
	{ 0x3736, 0x20 },
	{ 0x373a, 0x02 },
	{ 0x373b, 0x0c },
	{ 0x373c, 0x0a },
	{ 0x373e, 0x03 },
	{ 0x3755, 0x40 },
	{ 0x3758, 0x00 },
	{ 0x3759, 0x4c },
	{ 0x375a, 0x06 },
	{ 0x375b, 0x13 },
	{ 0x375c, 0x40 },
	{ 0x375d, 0x02 },
	{ 0x375e, 0x00 },
	{ 0x375f, 0x14 },
	{ 0x3767, 0x1c },
	{ 0x3768, 0x04 },
	{ 0x3769, 0x20 },
	{ 0x376c, 0xc0 },
	{ 0x376d, 0xc0 },
	{ 0x376a, 0x08 },
	{ 0x3761, 0x00 },
	{ 0x3762, 0x00 },
	{ 0x3763, 0x00 },
	{ 0x3766, 0xff },
	{ 0x376b, 0x42 },
	{ 0x3772, 0x23 },
	{ 0x3773, 0x02 },
	{ 0x3774, 0x16 },
	{ 0x3775, 0x12 },
	{ 0x3776, 0x08 },
	{ 0x37a0, 0x44 },
	{ 0x37a1, 0x3d },
	{ 0x37a2, 0x3d },
	{ 0x37a3, 0x01 },
	{ 0x37a4, 0x00 },
	{ 0x37a5, 0x08 },
	{ 0x37a6, 0x00 },
	{ 0x37a7, 0x44 },
	{ 0x37a8, 0x58 },
	{ 0x37a9, 0x58 },
	{ 0x3760, 0x00 },
	{ 0x376f, 0x01 },
	{ 0x37aa, 0x44 },
	{ 0x37ab, 0x2e },
	{ 0x37ac, 0x2e },
	{ 0x37ad, 0x33 },
	{ 0x37ae, 0x0d },
	{ 0x37af, 0x0d },
	{ 0x37b0, 0x00 },
	{ 0x37b1, 0x00 },
	{ 0x37b2, 0x00 },
	{ 0x37b3, 0x42 },
	{ 0x37b4, 0x42 },
	{ 0x37b5, 0x33 },
	{ 0x37b6, 0x00 },
	{ 0x37b7, 0x00 },
	{ 0x37b8, 0x00 },
	{ 0x37b9, 0xff },
	{ OV8865_OTP_REG, 0x06 },
	{ OV8865_OTP_SETT_STT_ADDR_H_REG, 0x75 },
	{ OV8865_OTP_SETT_STT_ADDR_L_REG, 0xef },
	{ 0x3f08, 0x0b },
	{ OV8865_CLIP_MAX_HI_REG, 0xff },
	{ OV8865_CLIP_MIN_HI_REG, 0x00 },
	{ OV8865_CLIP_LO_REG, 0x0f },
	{ 0x4500, 0x40 },
	{ 0x4503, 0x10 },
	{ OV8865_R_VFIFO_READ_START_REG, 0x74 },
	{ OV8865_CLK_PREPARE_MIN_REG, 0x32 },
	{ OV8865_PCLK_PERIOD_REG, 0x16 },
	{ OV8865_LANE_SEL01_REG, 0x10 },
	{ OV8865_LANE_SEL23_REG, 0x32 },
	{ OV8865_LVDS_R0_REG, 0x2a },
	{ OV8865_LVDS_BLK_TIMES_L_REG, 0x00 },
	{ 0x4d00, 0x04 },
	{ 0x4d01, 0x18 },
	{ 0x4d02, 0xc3 },
	{ 0x4d03, 0xff },
	{ 0x4d04, 0xff },
	{ 0x4d05, 0xff },
	{ OV8865_ISP_CTRL0_REG, 0x96 },
	{ OV8865_ISP_CTRL1_REG, 0x01 },
	{ OV8865_ISP_CTRL2_REG, 0x08 },
	{ 0x5901, 0x00 },
	{ OV8865_PRE_CTRL0, 0x00 },
	{ OV8865_PRE_CTRL1, 0x41 },
	{ OV8865_SW_STANDBY_REG, OV8865_SW_STANDBY_STANDBY_N },
	{ OV8865_OTP_CTRL0, 0x02 },
	{ OV8865_OTP_CTRL1, 0xd0 },
	{ OV8865_OTP_CTRL2, 0x03 },
	{ OV8865_OTP_CTRL3, 0xff },
	{ OV8865_OTP_CTRL5, 0x6c },
	{ 0x5780, 0xfc },
	{ 0x5781, 0xdf },
	{ 0x5782, 0x3f },
	{ 0x5783, 0x08 },
	{ 0x5784, 0x0c },
	{ 0x5786, 0x20 },
	{ 0x5787, 0x40 },
	{ 0x5788, 0x08 },
	{ 0x5789, 0x08 },
	{ 0x578a, 0x02 },
	{ 0x578b, 0x01 },
	{ 0x578c, 0x01 },
	{ 0x578d, 0x0c },
	{ 0x578e, 0x02 },
	{ 0x578f, 0x01 },
	{ 0x5790, 0x01 },
	{ OV8865_LENC_G0_REG, 0x1d },
	{ OV8865_LENC_G1_REG, 0x0e },
	{ OV8865_LENC_G2_REG, 0x0c },
	{ OV8865_LENC_G3_REG, 0x0c },
	{ OV8865_LENC_G4_REG, 0x0f },
	{ OV8865_LENC_G5_REG, 0x22 },
	{ OV8865_LENC_G10_REG, 0x0a },
	{ OV8865_LENC_G11_REG, 0x06 },
	{ OV8865_LENC_G12_REG, 0x05 },
	{ OV8865_LENC_G13_REG, 0x05 },
	{ OV8865_LENC_G14_REG, 0x07 },
	{ OV8865_LENC_G15_REG, 0x0a },
	{ OV8865_LENC_G20_REG, 0x06 },
	{ OV8865_LENC_G21_REG, 0x02 },
	{ OV8865_LENC_G22_REG, 0x00 },
	{ OV8865_LENC_G23_REG, 0x00 },
	{ OV8865_LENC_G24_REG, 0x03 },
	{ OV8865_LENC_G25_REG, 0x07 },
	{ OV8865_LENC_G30_REG, 0x06 },
	{ OV8865_LENC_G31_REG, 0x02 },
	{ OV8865_LENC_G32_REG, 0x00 },
	{ OV8865_LENC_G33_REG, 0x00 },
	{ OV8865_LENC_G34_REG, 0x03 },
	{ OV8865_LENC_G35_REG, 0x07 },
	{ OV8865_LENC_G40_REG, 0x09 },
	{ OV8865_LENC_G41_REG, 0x06 },
	{ OV8865_LENC_G42_REG, 0x04 },
	{ OV8865_LENC_G43_REG, 0x04 },
	{ OV8865_LENC_G44_REG, 0x06 },
	{ OV8865_LENC_G45_REG, 0x0a },
	{ OV8865_LENC_G50_REG, 0x19 },
	{ OV8865_LENC_G51_REG, 0x0d },
	{ OV8865_LENC_G52_REG, 0x0b },
	{ OV8865_LENC_G53_REG, 0x0b },
	{ OV8865_LENC_G54_REG, 0x0e },
	{ OV8865_LENC_G55_REG, 0x22 },
	{ OV8865_LENC_BR0_REG, 0x23 },
	{ OV8865_LENC_BR1_REG, 0x28 },
	{ OV8865_LENC_BR2_REG, 0x29 },
	{ OV8865_LENC_BR3_REG, 0x27 },
	{ OV8865_LENC_BR4_REG, 0x13 },
	{ OV8865_LENC_BR10_REG, 0x26 },
	{ OV8865_LENC_BR11_REG, 0x33 },
	{ OV8865_LENC_BR12_REG, 0x32 },
	{ OV8865_LENC_BR13_REG, 0x33 },
	{ OV8865_LENC_BR14_REG, 0x16 },
	{ OV8865_LENC_BR20_REG, 0x14 },
	{ OV8865_LENC_BR21_REG, 0x30 },
	{ OV8865_LENC_BR22_REG, 0x31 },
	{ OV8865_LENC_BR23_REG, 0x30 },
	{ OV8865_LENC_BR24_REG, 0x15 },
	{ OV8865_LENC_BR30_REG, 0x26 },
	{ OV8865_LENC_BR31_REG, 0x23 },
	{ OV8865_LENC_BR32_REG, 0x21 },
	{ OV8865_LENC_BR33_REG, 0x23 },
	{ OV8865_LENC_BR34_REG, 0x05 },
	{ OV8865_LENC_BR40_REG, 0x36 },
	{ OV8865_LENC_BR41_REG, 0x27 },
	{ OV8865_LENC_BR42_REG, 0x28 },
	{ OV8865_LENC_BR43_REG, 0x26 },
	{ OV8865_LENC_BR44_REG, 0x24 },
	{ OV8865_LENC_BROFFSET_REG, 0xdf },
	{ OV8865_SW_STANDBY_REG, 0x00 },
};

static const struct reg_value ov8865_setting_QUXGA[] = {
	{ OV8865_SW_STANDBY_REG, 0x00, 5 },
	{ 0x3501, 0x98 },
	{ 0x3502, 0x60 },
	{ 0x3700, 0x48 },
	{ 0x3701, 0x18 },
	{ 0x3702, 0x50 },
	{ 0x3703, 0x32 },
	{ 0x3704, 0x28 },
	{ 0x3706, 0x70 },
	{ 0x3707, 0x08 },
	{ 0x3708, 0x48 },
	{ 0x3709, 0x80 },
	{ 0x370a, 0x01 },
	{ 0x370b, 0x70 },
	{ 0x370c, 0x07 },
	{ 0x3718, 0x14 },
	{ 0x3712, 0x44 },
	{ 0x371e, 0x31 },
	{ 0x371f, 0x7f },
	{ 0x3720, 0x0a },
	{ 0x3721, 0x0a },
	{ 0x3724, 0x04 },
	{ 0x3725, 0x04 },
	{ 0x3726, 0x0c },
	{ 0x3728, 0x0a },
	{ 0x3729, 0x03 },
	{ 0x372a, 0x06 },
	{ 0x372b, 0xa6 },
	{ 0x372c, 0xa6 },
	{ 0x372d, 0xa6 },
	{ 0x372e, 0x0c },
	{ 0x372f, 0x20 },
	{ 0x3730, 0x02 },
	{ 0x3731, 0x0c },
	{ 0x3732, 0x28 },
	{ 0x3736, 0x30 },
	{ 0x373a, 0x04 },
	{ 0x373b, 0x18 },
	{ 0x373c, 0x14 },
	{ 0x373e, 0x06 },
	{ 0x375a, 0x0c },
	{ 0x375b, 0x26 },
	{ 0x375d, 0x04 },
	{ 0x375f, 0x28 },
	{ 0x3767, 0x1e },
	{ 0x3772, 0x46 },
	{ 0x3773, 0x04 },
	{ 0x3774, 0x2c },
	{ 0x3775, 0x13 },
	{ 0x3776, 0x10 },
	{ 0x37a0, 0x88 },
	{ 0x37a1, 0x7a },
	{ 0x37a2, 0x7a },
	{ 0x37a3, 0x02 },
	{ 0x37a5, 0x09 },
	{ 0x37a7, 0x88 },
	{ 0x37a8, 0xb0 },
	{ 0x37a9, 0xb0 },
	{ 0x37aa, 0x88 },
	{ 0x37ab, 0x5c },
	{ 0x37ac, 0x5c },
	{ 0x37ad, 0x55 },
	{ 0x37ae, 0x19 },
	{ 0x37af, 0x19 },
	{ 0x37b3, 0x84 },
	{ 0x37b4, 0x84 },
	{ 0x37b5, 0x66 },
	{ 0x3f08, 0x16 },
	{ 0x4500, 0x68 },
	{ OV8865_R_VFIFO_READ_START_REG, 0x10 },
	{ OV8865_ISP_CTRL2_REG, 0x08 },
	{ 0x5901, 0x00 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
};

static const struct reg_value ov8865_setting_6M[] = {
	{ OV8865_SW_STANDBY_REG, 0x00, 5 },
	{ 0x3501, 0x72 },
	{ 0x3502, 0x20 },
	{ 0x3700, 0x48 },
	{ 0x3701, 0x18 },
	{ 0x3702, 0x50 },
	{ 0x3703, 0x32 },
	{ 0x3704, 0x28 },
	{ 0x3706, 0x70 },
	{ 0x3707, 0x08 },
	{ 0x3708, 0x48 },
	{ 0x3709, 0x80 },
	{ 0x370a, 0x01 },
	{ 0x370b, 0x70 },
	{ 0x370c, 0x07 },
	{ 0x3718, 0x14 },
	{ 0x3712, 0x44 },
	{ 0x371e, 0x31 },
	{ 0x371f, 0x7f },
	{ 0x3720, 0x0a },
	{ 0x3721, 0x0a },
	{ 0x3724, 0x04 },
	{ 0x3725, 0x04 },
	{ 0x3726, 0x0c },
	{ 0x3728, 0x0a },
	{ 0x3729, 0x03 },
	{ 0x372a, 0x06 },
	{ 0x372b, 0xa6 },
	{ 0x372c, 0xa6 },
	{ 0x372d, 0xa6 },
	{ 0x372e, 0x0c },
	{ 0x372f, 0x20 },
	{ 0x3730, 0x02 },
	{ 0x3731, 0x0c },
	{ 0x3732, 0x28 },
	{ 0x3736, 0x30 },
	{ 0x373a, 0x04 },
	{ 0x373b, 0x18 },
	{ 0x373c, 0x14 },
	{ 0x373e, 0x06 },
	{ 0x375a, 0x0c },
	{ 0x375b, 0x26 },
	{ 0x375d, 0x04 },
	{ 0x375f, 0x28 },
	{ 0x3767, 0x1e },
	{ 0x3772, 0x46 },
	{ 0x3773, 0x04 },
	{ 0x3774, 0x2c },
	{ 0x3775, 0x13 },
	{ 0x3776, 0x10 },
	{ 0x37a0, 0x88 },
	{ 0x37a1, 0x7a },
	{ 0x37a2, 0x7a },
	{ 0x37a3, 0x02 },
	{ 0x37a5, 0x09 },
	{ 0x37a7, 0x88 },
	{ 0x37a8, 0xb0 },
	{ 0x37a9, 0xb0 },
	{ 0x37aa, 0x88 },
	{ 0x37ab, 0x5c },
	{ 0x37ac, 0x5c },
	{ 0x37ad, 0x55 },
	{ 0x37ae, 0x19 },
	{ 0x37af, 0x19 },
	{ 0x37b3, 0x84 },
	{ 0x37b4, 0x84 },
	{ 0x37b5, 0x66 },
	{ 0x3f08, 0x16 },
	{ 0x4500, 0x68 },
	{ OV8865_R_VFIFO_READ_START_REG, 0x10 },
	{ OV8865_ISP_CTRL2_REG, 0x08 },
	{ 0x5901, 0x00 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
};


static const struct reg_value ov8865_setting_UXGA[] = {
	{ OV8865_SW_STANDBY_REG, 0x00, 5 },
	{ 0x3501, 0x4c },
	{ 0x3502, 0x00 },
	{ 0x3700, 0x24 },
	{ 0x3701, 0x0c },
	{ 0x3702, 0x28 },
	{ 0x3703, 0x19 },
	{ 0x3704, 0x14 },
	{ 0x3706, 0x38 },
	{ 0x3707, 0x04 },
	{ 0x3708, 0x24 },
	{ 0x3709, 0x40 },
	{ 0x370a, 0x00 },
	{ 0x370b, 0xb8 },
	{ 0x370c, 0x04 },
	{ 0x3718, 0x12 },
	{ 0x3712, 0x42 },
	{ 0x371e, 0x19 },
	{ 0x371f, 0x40 },
	{ 0x3720, 0x05 },
	{ 0x3721, 0x05 },
	{ 0x3724, 0x02 },
	{ 0x3725, 0x02 },
	{ 0x3726, 0x06 },
	{ 0x3728, 0x05 },
	{ 0x3729, 0x02 },
	{ 0x372a, 0x03 },
	{ 0x372b, 0x53 },
	{ 0x372c, 0xa3 },
	{ 0x372d, 0x53 },
	{ 0x372e, 0x06 },
	{ 0x372f, 0x10 },
	{ 0x3730, 0x01 },
	{ 0x3731, 0x06 },
	{ 0x3732, 0x14 },
	{ 0x3736, 0x20 },
	{ 0x373a, 0x02 },
	{ 0x373b, 0x0c },
	{ 0x373c, 0x0a },
	{ 0x373e, 0x03 },
	{ 0x375a, 0x06 },
	{ 0x375b, 0x13 },
	{ 0x375d, 0x02 },
	{ 0x375f, 0x14 },
	{ 0x3767, 0x1c },
	{ 0x3772, 0x23 },
	{ 0x3773, 0x02 },
	{ 0x3774, 0x16 },
	{ 0x3775, 0x12 },
	{ 0x3776, 0x08 },
	{ 0x37a0, 0x44 },
	{ 0x37a1, 0x3d },
	{ 0x37a2, 0x3d },
	{ 0x37a3, 0x01 },
	{ 0x37a5, 0x08 },
	{ 0x37a7, 0x44 },
	{ 0x37a8, 0x58 },
	{ 0x37a9, 0x58 },
	{ 0x37aa, 0x44 },
	{ 0x37ab, 0x2e },
	{ 0x37ac, 0x2e },
	{ 0x37ad, 0x33 },
	{ 0x37ae, 0x0d },
	{ 0x37af, 0x0d },
	{ 0x37b3, 0x42 },
	{ 0x37b4, 0x42 },
	{ 0x37b5, 0x33 },
	{ 0x3f08, 0x0b },
	{ 0x4500, 0x40 },
	{ OV8865_R_VFIFO_READ_START_REG, 0x74 },
	{ OV8865_ISP_CTRL2_REG, 0x08 },
	{ 0x5901, 0x00 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
};

static const struct reg_value ov8865_setting_SVGA[] = {
	{ OV8865_SW_STANDBY_REG, 0x00, 5 },
	{ 0x3501, 0x26 },
	{ 0x3502, 0x00 },
	{ 0x3700, 0x24 },
	{ 0x3701, 0x0c },
	{ 0x3702, 0x28 },
	{ 0x3703, 0x19 },
	{ 0x3704, 0x14 },
	{ 0x3706, 0x38 },
	{ 0x3707, 0x04 },
	{ 0x3708, 0x24 },
	{ 0x3709, 0x40 },
	{ 0x370a, 0x00 },
	{ 0x370b, 0xb8 },
	{ 0x370c, 0x04 },
	{ 0x3718, 0x12 },
	{ 0x3712, 0x42 },
	{ 0x371e, 0x19 },
	{ 0x371f, 0x40 },
	{ 0x3720, 0x05 },
	{ 0x3721, 0x05 },
	{ 0x3724, 0x02 },
	{ 0x3725, 0x02 },
	{ 0x3726, 0x06 },
	{ 0x3728, 0x05 },
	{ 0x3729, 0x02 },
	{ 0x372a, 0x03 },
	{ 0x372b, 0x53 },
	{ 0x372c, 0xa3 },
	{ 0x372d, 0x53 },
	{ 0x372e, 0x06 },
	{ 0x372f, 0x10 },
	{ 0x3730, 0x01 },
	{ 0x3731, 0x06 },
	{ 0x3732, 0x14 },
	{ 0x3736, 0x20 },
	{ 0x373a, 0x02 },
	{ 0x373b, 0x0c },
	{ 0x373c, 0x0a },
	{ 0x373e, 0x03 },
	{ 0x375a, 0x06 },
	{ 0x375b, 0x13 },
	{ 0x375d, 0x02 },
	{ 0x375f, 0x14 },
	{ 0x3767, 0x18 },
	{ 0x3772, 0x23 },
	{ 0x3773, 0x02 },
	{ 0x3774, 0x16 },
	{ 0x3775, 0x12 },
	{ 0x3776, 0x08 },
	{ 0x37a0, 0x44 },
	{ 0x37a1, 0x3d },
	{ 0x37a2, 0x3d },
	{ 0x37a3, 0x01 },
	{ 0x37a5, 0x08 },
	{ 0x37a7, 0x44 },
	{ 0x37a8, 0x58 },
	{ 0x37a9, 0x58 },
	{ 0x37aa, 0x44 },
	{ 0x37ab, 0x2e },
	{ 0x37ac, 0x2e },
	{ 0x37ad, 0x33 },
	{ 0x37ae, 0x0d },
	{ 0x37af, 0x0d },
	{ 0x37b3, 0x42 },
	{ 0x37b4, 0x42 },
	{ 0x37b5, 0x33 },
	{ 0x3f08, 0x0b },
	{ 0x4500, 0x40 },
	{ OV8865_R_VFIFO_READ_START_REG, 0x50 },
	{ OV8865_ISP_CTRL2_REG, 0x0c },
	{ 0x5901, 0x04 },
	{ OV8865_SW_STANDBY_REG, 0x00 },
};

static const struct ov8865_mode_info ov8865_mode_init_data = {
	.id = 0,
	.hact = 3264,
	.htot = 1944,
	.vact = 2448,
	.vtot = 2470,
	.reg_data = ov8865_init_setting_QUXGA,
	.reg_data_size = ARRAY_SIZE(ov8865_init_setting_QUXGA),
};

static const struct ov8865_mode_info ov8865_mode_data[OV8865_NUM_MODES] = {
	{
		.id = OV8865_MODE_QUXGA_3264_2448,
		.hact = 3264,
		.htot = 1944,
		.vact = 2448,
		.vtot = 2470,
		.reg_data = ov8865_setting_QUXGA,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_QUXGA)
	},
	{
		.id = OV8865_MODE_6M_3264_1836,
		.hact = 3264,
		.htot = 2582,
		.vact = 1836,
		.vtot = 1858,
		.reg_data = ov8865_setting_6M,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_6M)
	},
	{
		.id = OV8865_MODE_1080P_1920_1080,
		.hact = 1920,
		.htot = 2582,
		.vact = 1080,
		.vtot = 1858,
		.reg_data = ov8865_setting_6M,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_6M)
	},
	{
		.id = OV8865_MODE_720P_1280_720,
		.hact = 1280,
		.htot = 1923,
		.vact = 720,
		.vtot = 1248,
		.reg_data = ov8865_setting_UXGA,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_UXGA)
	},
	{
		.id = OV8865_MODE_UXGA_1600_1200,
		.hact = 1600,
		.htot = 1923,
		.vact = 1200,
		.vtot = 1248,
		.reg_data = ov8865_setting_UXGA,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_UXGA)
	},
	{
		.id = OV8865_MODE_SVGA_800_600,
		.hact = 800,
		.htot = 1250,
		.vact = 600,
		.vtot = 640,
		.reg_data = ov8865_setting_SVGA,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_SVGA)
	},
	{
		.id = OV8865_MODE_VGA_640_480,
		.hact = 640,
		.htot = 2582,
		.vact = 480,
		.vtot = 1858,
		.reg_data = ov8865_setting_6M,
		.reg_data_size = ARRAY_SIZE(ov8865_setting_6M)
	},
};

static int ov8865_write_reg(struct ov8865_dev *sensor, u16 reg, u8 val)
{
	struct i2c_client *client = sensor->i2c_client;
	struct i2c_msg msg = { 0 };
	u8 buf[3];
	int ret;

	buf[0] = reg >> 8;
	buf[1] = reg & 0xff;
	buf[2] = val;

	msg.addr = client->addr;
	msg.flags = client->flags;
	msg.buf = buf;
	msg.len = sizeof(buf);

	ret = i2c_transfer(client->adapter, &msg, 1);
	if (ret < 0) {
		dev_err(&client->dev, "%s: error: reg=%x, val=%x\n",
			__func__, reg, val);
		return ret;
	}

	return 0;
}

static int ov8865_write_reg16(struct ov8865_dev *sensor, u16 reg, u16 val)
{
	int ret;

	ret = ov8865_write_reg(sensor, reg, val >> 8);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, reg + 1, val & 0xff);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_read_reg(struct ov8865_dev *sensor, u16 reg, u8 *val)
{
	struct i2c_client *client = sensor->i2c_client;
	struct i2c_msg msg[2]  = { 0 };
	u8 buf[2];
	int ret = 0;

	buf[0] = reg >> 8;
	buf[1] = reg & 0xff;

	msg[0].addr = client->addr;
	msg[0].flags = client->flags;
	msg[0].buf = buf;
	msg[0].len = sizeof(buf);

	msg[1].addr = client->addr;
	/* Read data from the sensor to the controller */
	msg[1].flags =  I2C_M_RD;
	msg[1].buf = buf;
	msg[1].len = 1;

	ret = i2c_transfer(client->adapter, msg, 2);
	if (ret < 0) {
		dev_err(&client->dev, "%s: error: reg=%x\n", __func__, reg);
		return ret;
	}

	*val = buf[0];

	return 0;
}

static int ov8865_read_reg16(struct ov8865_dev *sensor, u16 reg, u16 *val)
{
	u8 hi, lo;
	int ret;

	ret = ov8865_read_reg(sensor, reg, &hi);
	if (ret)
		return ret;

	ret = ov8865_read_reg(sensor, reg + 1, &lo);
	if (ret)
		return ret;

	*val = ((u16)hi << 8) | (u16)lo;

	return 0;
}

static int ov8865_mod_reg(struct ov8865_dev *sensor, u16 reg, u8 mask, u8 val)
{
	u8 readval;
	int ret;

	ret = ov8865_read_reg(sensor, reg, &readval);
	if (ret)
		return ret;

	readval &= ~mask;
	val &= mask;
	val |= readval;

	ret = ov8865_write_reg(sensor, reg, val);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_set_timings(struct ov8865_dev *sensor,
			      const struct ov8865_mode_info *mode)
{
	int ret;
	u8 isp_y_win_l, x_inc_odd, format2, y_inc_odd,
	   y_inc_even, blc_num_option, zline_num_option,
	   boundary_pix_num;

	ret = ov8865_write_reg(sensor, OV8865_X_ADDR_START_H_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_X_ADDR_START_L_REG, 0x0c);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_START_H_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_START_L_REG, 0x0c);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_X_ADDR_END_H_REG, 0x0c);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_X_ADDR_END_L_REG, 0xd3);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_END_H_REG, 0x09);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_Y_ADDR_END_L_REG, 0xa3);
	if (ret)
		return ret;

	ret = ov8865_write_reg16(sensor, OV8865_X_OUTPUT_SIZE_REG, mode->hact);
	if (ret)
		return ret;

	ret = ov8865_write_reg16(sensor, OV8865_Y_OUTPUT_SIZE_REG, mode->vact);
	if (ret)
		return ret;

	ret = ov8865_write_reg16(sensor, OV8865_HTS_REG, mode->htot);
	if (ret)
		return ret;

	ret = ov8865_write_reg16(sensor, OV8865_VTS_REG, mode->vtot);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ISP_X_WIN_H_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ISP_X_WIN_L_REG, 0x04);
	if (ret)
		return ret;

	if ((mode->id == OV8865_MODE_720P_1280_720) ||
	    (mode->id == OV8865_MODE_UXGA_1600_1200) ||
	    (mode->id == OV8865_MODE_SVGA_800_600)) {
		isp_y_win_l = 0x04;
		x_inc_odd = 0x03;
		blc_num_option = 0x08;
		zline_num_option = 0x02;
		boundary_pix_num = 0x88;

	} else {
		isp_y_win_l = 0x02;
		x_inc_odd = 0x01;
		blc_num_option = 0x04;
		zline_num_option = 0x01;
		boundary_pix_num = 0x48;
	}

	ret = ov8865_write_reg(sensor, OV8865_ISP_Y_WIN_L_REG, isp_y_win_l);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_X_INC_ODD_REG, x_inc_odd);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_X_INC_EVEN_REG, 0x01);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_FORMAT1_REG, 0x00);
	if (ret)
		return ret;

	if ((mode->id == OV8865_MODE_720P_1280_720) ||
	    (mode->id == OV8865_MODE_UXGA_1600_1200)) {
		format2 = 0x67;
		y_inc_odd = 0x03;
	} else if (mode->id == OV8865_MODE_SVGA_800_600) {
		format2 = 0x6f;
		y_inc_odd = 0x05;
	} else {
		format2 = 0x46;
		y_inc_odd = 0x01;
	}

	ret = ov8865_write_reg(sensor, OV8865_FORMAT2_REG, format2);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_Y_INC_ODD_REG, y_inc_odd);
	if (ret)
		return ret;

	if (mode->id == OV8865_MODE_SVGA_800_600)
		y_inc_even = 0x03;
	else
		y_inc_even = 0x01;

	ret = ov8865_write_reg(sensor, OV8865_Y_INC_EVEN_REG, y_inc_even);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_NUM_OPTION_REG,
			       blc_num_option);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ZLINE_NUM_OPTION_REG,
			       zline_num_option);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_RGBC_REG, 0x18);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_AUTO_SIZE_CTRL0_REG, 0xff);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BOUNDARY_PIX_NUM_REG,
			       boundary_pix_num);

	return 0;
}

static int ov8865_get_hts(struct ov8865_dev *sensor)
{
	u16 hts;
	int ret;

	ret = ov8865_read_reg16(sensor, OV8865_HTS_REG, &hts);
	if (ret)
		return ret;
	return hts;
}

static int ov8865_load_regs(struct ov8865_dev *sensor,
			     const struct ov8865_mode_info *mode)
{
	const struct reg_value *regs = mode->reg_data;
	unsigned int i;
	u32 delay_ms = 0;
	u16 reg_addr;
	u8 val;
	int ret = 0;

	for (i = 0; i < mode->reg_data_size; i++, regs++) {
		delay_ms = regs->delay_ms;
		reg_addr = regs->reg_addr;
		val = regs->val;

		ret = ov8865_write_reg(sensor, reg_addr, val);
		if (ret)
			return ret;

		if (delay_ms)
			usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
	}

	return 0;
}

static const struct ov8865_mode_info *
ov8865_find_mode(struct ov8865_dev *sensor, enum ov8865_frame_rate fr,
		 int width, int height, bool nearest)
{
	const struct ov8865_mode_info *mode;

	mode = v4l2_find_nearest_size(ov8865_mode_data,
				      ARRAY_SIZE(ov8865_mode_data),
				      hact, vact, width, height);

	if (!mode || (!nearest && (mode->hact != width || mode->vact !=
				   height)))
		return NULL;

	/* Only SVGA can operate 90 fps. */
	if (fr == OV8865_90_FPS && !(mode->hact == 800 && mode->vact == 600))
		return NULL;

	return mode;
}

static u64 ov8865_calc_pixel_rate(struct ov8865_dev *sensor)
{
	u64 rate;

	rate = sensor->current_mode->vtot * sensor->current_mode->htot;
	rate *= ov8865_framerates[sensor->current_fr];

	return rate;
}

static int ov8865_set_mode_direct(struct ov8865_dev *sensor,
			      const struct ov8865_mode_info *mode)
{
	int ret;

	if (!mode->reg_data)
		return -EINVAL;

	/*Write capture setting*/
	ret = ov8865_load_regs(sensor, mode);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_set_black_level(struct ov8865_dev *sensor)
{
	const struct ov8865_mode_info *mode = sensor->current_mode;
	int ret;
	u8 blc_ctrl1, left_start_h, left_start_l, left_end_h,
	   left_end_l, right_start_h, right_start_l,
	   right_end_h, right_end_l, bkline_num, bkline_st,
	   zline_st, zline_num, blkline_st;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL0_REG, 0xf1);
	if (ret)
		return ret;

	if ((mode->id == OV8865_MODE_QUXGA_3264_2448) ||
	    (mode->id == OV8865_MODE_6M_3264_1836) ||
	    (mode->id == OV8865_MODE_1080P_1920_1080) ||
	    (mode->id == OV8865_MODE_VGA_640_480))
		blc_ctrl1 = 0x04;
	else
		blc_ctrl1 = 0x14;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1_REG, blc_ctrl1);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL5_REG, 0x10);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRLB_REG, 0x0c);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRLD_REG, 0x10);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1B_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1D_REG, 0x00);
	if (ret)
		return ret;

	if ((mode->id  == OV8865_MODE_QUXGA_3264_2448) ||
	    (mode->id  == OV8865_MODE_6M_3264_1836) ||
	    (mode->id == OV8865_MODE_1080P_1920_1080) ||
	    (mode->id == OV8865_MODE_VGA_640_480)) {
		left_start_h = 0x02;
		left_start_l = 0x40;
		left_end_h = 0x03;
		left_end_l = 0x3f;
		right_start_h = 0x07;
		right_start_l = 0xc0;
		right_end_h = 0x08;
		right_end_l = 0xbf;
	} else {
		left_start_h = 0x01;
		left_start_l = 0x20;
		left_end_h = 0x01;
		left_end_l = 0x9f;
		right_start_h = 0x03;
		right_start_l = 0xe0;
		right_end_h = 0x04;
		right_end_l = 0x5f;
	}

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_START_H_REG,
			       left_start_h);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_START_L_REG,
			       left_start_l);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_END_H_REG,
			       left_end_h);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_LEFT_END_L_REG,
			       left_end_l);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_START_H_REG,
			       right_start_h);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_START_L_REG,
			       right_start_l);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_END_H_REG,
			       right_end_h);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_ANCHOR_RIGHT_END_L_REG,
			       right_end_l);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_TOP_ZLINE_ST_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_TOP_ZLINE_NUM_REG, 0x02);
	if (ret)
		return ret;

	if (mode->id == OV8865_MODE_SVGA_800_600) {
		bkline_st = 0x02;
		bkline_num = 0x02;
		zline_st = 0x00;
		zline_num = 0x00;
		blkline_st = 0x04;
	} else {
		bkline_st = 0x04;
		bkline_num = 0x04;
		zline_st = 0x02;
		zline_num = 0x02;
		blkline_st = 0x08;
	}
	ret = ov8865_write_reg(sensor, OV8865_TOP_BKLINE_ST_REG, bkline_st);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_TOP_BKLINE_NUM_REG, bkline_num);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BOT_ZLINE_ST_REG, zline_st);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BOT_ZLINE_NUM_REG, zline_num);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BOT_BLKLINE_ST_REG, blkline_st);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BOT_BLKLINE_NUM_REG, 0x02);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_CTRL1F_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_BLC_OFFSET_LIMIT_REG, 0x3f);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_set_pclk(struct ov8865_dev *sensor)
{
	int ret;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL2_REG, 0x1e);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL3_REG, 0x00);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL4_REG, 0x03);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_CLOCK_SEL_REG, 0x93);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_get_pclk(struct ov8865_dev *sensor)
{
	int ret;
	u8 pll1_mult, m_div, mipi_div_r, mipi_div, pclk_div_r, pclk_div;
	int ref_clk = OV8865_XCLK_FREQ / 1000000;

	ret = ov8865_read_reg(sensor, OV8865_PLL_CTRL2_REG, &pll1_mult);
	if (ret)
		return ret;

	ret = ov8865_read_reg(sensor, OV8865_PLL_CTRL3_REG, &m_div);
	if (ret)
		return ret;

	m_div = m_div & 0x07;
	ret = ov8865_read_reg(sensor, OV8865_PLL_CTRL4_REG, &mipi_div_r);
	if (ret)
		return ret;

	mipi_div_r = mipi_div_r & 0x03;

	if (mipi_div_r == 0x00)
		mipi_div = 4;

	if (mipi_div_r == 0x01)
		mipi_div = 5;

	if (mipi_div_r == 0x02)
		mipi_div = 6;

	if (mipi_div_r == 0x03)
		mipi_div = 8;

	ret = ov8865_read_reg(sensor,  OV8865_CLOCK_SEL_REG, &pclk_div_r);
	if (ret)
		return ret;

	pclk_div_r = (pclk_div_r & 0x08) >> 3;

	if (pclk_div_r == 0)
		pclk_div = 1;

	if (pclk_div_r == 1)
		pclk_div = 2;

	return ref_clk * pll1_mult / (1 + m_div) / mipi_div / pclk_div;
}

static int ov8865_set_sclk(struct ov8865_dev *sensor)
{
	const struct ov8865_mode_info *mode = sensor->current_mode;
	int ret;
	u8 val;

	if ((mode->id  == OV8865_MODE_UXGA_1600_1200) ||
	    (mode->id == OV8865_MODE_720P_1280_720) ||
	    (mode->id == OV8865_MODE_SVGA_800_600))
		val = 0x09;
	else
		val = 0x04;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRLF_REG, val);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL12_REG, 0x01);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRL1E_REG, 0x0c);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_PLL_CTRLE_REG, 0x00);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_set_virtual_channel(struct ov8865_dev *sensor, u8 channel)
{
	u8 channel_id;
	int ret;

	ret = ov8865_read_reg(sensor, OV8865_MIPI_CTRL13_REG, &channel_id);
	if (ret)
		return ret;

	ret = ov8865_write_reg(sensor, OV8865_MIPI_CTRL13_REG, channel_id |
				channel);
	if (ret)
		return ret;

	return 0;
}

static int ov8865_set_mode(struct ov8865_dev *sensor)
{
	const struct ov8865_mode_info *mode = sensor->current_mode;
	int ret;

	ret = ov8865_set_pclk(sensor);
	if (ret < 0)
		return ret;

	ret = ov8865_set_sclk(sensor);
	if (ret < 0)
		return ret;

	ret = ov8865_set_black_level(sensor);
	if (ret)
		return ret;

	ret = ov8865_set_timings(sensor, mode);
	if (ret)
		return ret;

	ret = ov8865_set_mode_direct(sensor, mode);
	if (ret < 0)
		return ret;

	ret = ov8865_set_virtual_channel(sensor, 0);
	if (ret < 0)
		return ret;

	sensor->last_mode = mode;
	return 0;
}

static int ov8865_restore_mode(struct ov8865_dev *sensor)
{
	int ret;

	ret = ov8865_load_regs(sensor, &ov8865_mode_init_data);
	if (ret)
		return ret;

	sensor->last_mode = &ov8865_mode_init_data;

	ret = ov8865_set_mode(sensor);
	if (ret)
		return ret;

	return 0;
}

static void ov8865_power(struct ov8865_dev *sensor, bool enable)
{
	gpiod_set_value_cansleep(sensor->pwdn_gpio, enable ? 0 : 1);
}

static void ov8865_reset(struct ov8865_dev *sensor, bool enable)
{
	gpiod_set_value_cansleep(sensor->reset_gpio, enable ? 0 : 1);
}

/* Get GPIOs defined in dep_dev _CRS */
static int gpio_crs_get(struct ov8865_dev *sensor, struct device *dep_dev)
{
	sensor->dep_gpios = devm_gpiod_get_array(dep_dev, NULL, GPIOD_ASIS);
	if (IS_ERR(sensor->dep_gpios)) {
		dev_err(dep_dev, "Failed to get GPIOs\n");
		return -ENODEV;
	}

	return 0;
}

/* Put GPIOs defined in dep_dev _CRS */
static void gpio_crs_put(struct ov8865_dev *sensor)
{
	gpiod_put_array(sensor->dep_gpios);
}

/* Control GPIOs defined in dep_dev _CRS */
static int gpio_crs_ctrl(struct ov8865_dev *sensor, bool flag)
{
	struct gpio_descs *d = sensor->dep_gpios;
	unsigned long *values;

	values = bitmap_alloc(d->ndescs, GFP_KERNEL);
	if (!values)
		return -ENOMEM;

	if (flag)
		bitmap_fill(values, d->ndescs);
	else
		bitmap_zero(values, d->ndescs);

	gpiod_set_array_value_cansleep(d->ndescs, d->desc,
				       d->info, values);

	return 0;
}

static int ov8865_set_power_on(struct ov8865_dev *sensor)
{
	struct i2c_client *client = sensor->i2c_client;
	int ret = 0;

	/* For DT-based systems */
	if (!sensor->is_acpi_based) {
		ov8865_power(sensor, false);
		ov8865_reset(sensor, false);

		ret = clk_prepare_enable(sensor->xclk);
		if (ret) {
			dev_err(&client->dev, "%s: failed to enable clock\n",
				__func__);
			return ret;
		}

		ov8865_power(sensor, true);

		ret = regulator_bulk_enable(OV8865_NUM_SUPPLIES, sensor->supplies);
		if (ret) {
			dev_err(&client->dev, "%s: failed to enable regulators\n",
				__func__);
			goto err_power_off;
		}

		ov8865_reset(sensor, true);
		usleep_range(10000, 12000);
	}

	/* For ACPI-based systems */
	if (sensor->is_acpi_based) {
		gpio_crs_ctrl(sensor, true);

		/* Add some delay. This is required or check_chip_id() will fail. */
		usleep_range(10000, 12000);
	}

	return 0;

err_power_off:
	/* For DT-based systems */
	if (!sensor->is_acpi_based) {
		ov8865_power(sensor, false);
		clk_disable_unprepare(sensor->xclk);
	}
	return ret;
}

static void ov8865_set_power_off(struct ov8865_dev *sensor)
{
	/* For DT-based systems */
	if (!sensor->is_acpi_based) {
		ov8865_power(sensor, false);
		regulator_bulk_disable(OV8865_NUM_SUPPLIES, sensor->supplies);
		clk_disable_unprepare(sensor->xclk);
	}

	/* For ACPI-based systems */
	if (sensor->is_acpi_based)
		gpio_crs_ctrl(sensor, false);
}

static int ov8865_set_power(struct ov8865_dev *sensor, bool on)
{
	int ret = 0;

	if (on) {
		ret = ov8865_set_power_on(sensor);
		if (ret)
			return ret;

		ret = ov8865_restore_mode(sensor);
		if (ret)
			goto err_power_off;
	} else {
		ov8865_set_power_off(sensor);
	}

	return 0;

err_power_off:
	ov8865_set_power_off(sensor);
	return ret;
}

static int ov8865_s_power(struct v4l2_subdev *sd, int on)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	int ret = 0;

	mutex_lock(&sensor->lock);
	if (sensor->power_count == !on) {
		ret = ov8865_set_power(sensor, !!on);
		if (ret)
			goto out;
	}

	/* Update the power count. */
	sensor->power_count += on ? 1 : -1;
	WARN_ON(sensor->power_count < 0);
out:
	mutex_unlock(&sensor->lock);

	if (on && !ret && sensor->power_count == 1) {
		/* Initialize the hardware. */
		ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler);
	}

	return ret;
}

static int ov8865_try_frame_interval(struct ov8865_dev *sensor,
				     struct v4l2_fract *fi,
				     u32 width, u32 height)
{
	const struct ov8865_mode_info *mode;
	enum ov8865_frame_rate rate = OV8865_30_FPS;
	int minfps, maxfps, best_fps, fps;
	int i;

	minfps = ov8865_framerates[OV8865_30_FPS];
	maxfps = ov8865_framerates[OV8865_90_FPS];

	if (fi->numerator == 0) {
		fi->denominator = maxfps;
		fi->numerator = 1;
		rate = OV8865_90_FPS;
		goto find_mode;
	}

	fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator),
			minfps, maxfps);

	best_fps = minfps;
	for (i = 0; i < ARRAY_SIZE(ov8865_framerates); i++) {
		int curr_fps = ov8865_framerates[i];

		if (abs(curr_fps - fps) < abs(best_fps - fps)) {
			best_fps = curr_fps;
			rate = i;
		}
	}

	fi->numerator = 1;
	fi->denominator = best_fps;

find_mode:
	mode = ov8865_find_mode(sensor, rate, width, height, false);

	return mode ? rate : -EINVAL;
}

static int ov8865_try_fmt_internal(struct v4l2_subdev *sd,
				   struct v4l2_mbus_framefmt *fmt,
				   enum ov8865_frame_rate fr,
				   const struct ov8865_mode_info **new_mode)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	const struct ov8865_mode_info *mode;
	int i;

	mode = ov8865_find_mode(sensor, fr, fmt->width, fmt->height, true);
	if (!mode)
		return -EINVAL;

	fmt->width = mode->hact;
	fmt->height = mode->vact;

	if (new_mode)
		*new_mode = mode;

	for (i = 0; i < ARRAY_SIZE(ov8865_formats); i++)
		if (ov8865_formats[i].code == fmt->code)
			break;

	if (i == ARRAY_SIZE(ov8865_formats))
		i = 0;

	fmt->code = ov8865_formats[i].code;
	fmt->colorspace = ov8865_formats[i].colorspace;
	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
	fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);

	return 0;
}

static int ov8865_get_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_pad_config *cfg,
			  struct v4l2_subdev_format *format)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	struct v4l2_mbus_framefmt *fmt;

	if (format->pad != 0)
		return -EINVAL;

	mutex_lock(&sensor->lock);
	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
		fmt = v4l2_subdev_get_try_format(&sensor->sd, cfg,
						 format->pad);
	else
		fmt = &sensor->fmt;

	if (fmt)
		format->format = *fmt;

	mutex_unlock(&sensor->lock);

	return fmt ? 0 : -EINVAL;
}

static int ov8865_set_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_pad_config *cfg,
			  struct v4l2_subdev_format *format)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	const struct ov8865_mode_info *new_mode;
	struct v4l2_mbus_framefmt *mbus_fmt = &format->format;
	struct v4l2_mbus_framefmt *fmt;
	int ret;

	if (format->pad != 0)
		return -EINVAL;

	mutex_lock(&sensor->lock);

	if (sensor->streaming) {
		ret = -EBUSY;
		goto out;
	}

	ret = ov8865_try_fmt_internal(sd, mbus_fmt, sensor->current_fr,
				      &new_mode);
	if (ret)
		goto out;

	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
		fmt = v4l2_subdev_get_try_format(sd, cfg, 0);
	else
		fmt = &sensor->fmt;

	if (fmt)
		*fmt = *mbus_fmt;
	else
		ret = -EINVAL;

	if (new_mode != sensor->current_mode)
		sensor->current_mode = new_mode;

	__v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate,
				 ov8865_calc_pixel_rate(sensor));

out:
	mutex_unlock(&sensor->lock);
	return ret;
}

static int ov8865_set_ctrl_hflip(struct ov8865_dev *sensor, int value)
{
	return ov8865_mod_reg(sensor, OV8865_FORMAT2_REG,
			      OV8865_FORMAT2_MIRROR_DIG |
			      OV8865_FORMAT2_MIRROR_ARR,
			      (!(value ^ sensor->upside_down)) ?
			      (OV8865_FORMAT2_MIRROR_DIG |
			       OV8865_FORMAT2_MIRROR_ARR) : 0);
}

static int ov8865_set_ctrl_vflip(struct ov8865_dev *sensor, int value)
{
	return ov8865_mod_reg(sensor, OV8865_FORMAT1_REG,
			      OV8865_FORMAT1_MIRROR_DIG |
			      OV8865_FORMAT1_MIRROR_ARR,
			      (value ^ sensor->upside_down) ?
			      (OV8865_FORMAT2_MIRROR_DIG |
			       OV8865_FORMAT2_MIRROR_ARR) : 0);
}

static int ov8865_get_exposure(struct ov8865_dev *sensor)
{
	int exp, ret, pclk, hts, line_time;
	u8 temp;

	ret = ov8865_read_reg(sensor, OV8865_EXPOSURE_CTRL_HH_REG, &temp);
	if (ret)
		return ret;
	exp = ((int)temp & 0x0f) << 16;

	ret = ov8865_read_reg(sensor, OV8865_EXPOSURE_CTRL_H_REG, &temp);
	if (ret)
		return ret;
	exp |= ((int)temp << 8);

	ret = ov8865_read_reg(sensor, OV8865_EXPOSURE_CTRL_L_REG, &temp);
	if (ret)
		return ret;
	exp |= (int)temp;

	ret = ov8865_get_pclk(sensor);
	if (ret <= 0)
		return ret;

	pclk = ret;

	ret = ov8865_get_hts(sensor);
	if (ret <= 0)
		return ret;

	hts = ret;

	line_time = hts / pclk;

	/* The low 4 bits of exposure are the fractional part. And the unit is
	 * 1/16 of a line lecture time. The pclk and HTS are used to calculate
	 * this time. For V4L2, the value 1 of exposure stands for 100us of
	 * capture.
	 */
	return (exp >> 4) * line_time / 16 / 100;
}

static int ov8865_get_gain(struct ov8865_dev *sensor)
{
	u16 gain;
	int ret;

	/* Linear gain. */
	ret = ov8865_read_reg16(sensor, OV8865_GAIN_CTRL_H_REG, &gain);
	if (ret)
		return ret;

	return gain & 0x1fff;
}

static int ov8865_set_ctrl_exp(struct ov8865_dev *sensor)
{
	struct ov8865_ctrls *ctrls = &sensor->ctrls;
	int ret = 0, hts, pclk, line_time;
	int exposure = ctrls->exposure->val;
	/* The low 4 bits of exposure are the fractional part. And the unit is
	 * 1/16 of a line lecture time. The pclk and HTS are used to calculate
	 * this time. For V4L2, the value 1 of exposure stands for 100us of
	 * capture.
	 */

	ret = ov8865_get_pclk(sensor);
	if (ret <= 0)
		return ret;
	pclk = ret;

	ret = ov8865_get_hts(sensor);
	if (ret <= 0)
		return ret;
	hts = ret;

	line_time = hts / pclk;

	exposure = ctrls->exposure->val * 16 / line_time * 100;
	exposure = (exposure << 4);

	if (ctrls->exposure->is_new) {
		ret = ov8865_write_reg(sensor, OV8865_EXPOSURE_CTRL_L_REG,
				       exposure & 0xff);
		if (ret)
			return ret;

		ret = ov8865_write_reg(sensor, OV8865_EXPOSURE_CTRL_H_REG,
				       (exposure >> 8) & 0xff);
		if (ret)
			return ret;

		ret = ov8865_write_reg(sensor, OV8865_EXPOSURE_CTRL_HH_REG,
				       (exposure >> 16) & 0x0f);
	}

	return ret;
}

static int ov8865_set_ctrl_gain(struct ov8865_dev *sensor)
{
	struct ov8865_ctrls *ctrls = &sensor->ctrls;
	int ret = 0;
	int val = ctrls->gain->val;

	/* Linear gain. */
	if (ctrls->gain->is_new)
		ret = ov8865_write_reg16(sensor, OV8865_GAIN_CTRL_H_REG,
					 (u16)val & 0x1fff);
	return ret;
}

static int ov8865_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	int val;

	switch (ctrl->id) {
	case V4L2_CID_GAIN:
		val = ov8865_get_gain(sensor);
		if (val < 0)
			return val;
		sensor->ctrls.gain->val = val;
		break;
	case V4L2_CID_EXPOSURE:
		val = ov8865_get_exposure(sensor);
		if (val < 0)
			return val;
		sensor->ctrls.exposure->val = val;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl)
{
	struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	int ret;

	if (sensor->power_count == 0)
		return 0;

	switch (ctrl->id) {
	case V4L2_CID_GAIN:
		ret = ov8865_set_ctrl_gain(sensor);
		break;
	case V4L2_CID_EXPOSURE:
		ret = ov8865_set_ctrl_exp(sensor);
		break;
	case V4L2_CID_HFLIP:
		ret = ov8865_set_ctrl_hflip(sensor, ctrl->val);
		break;
	case V4L2_CID_VFLIP:
		ret = ov8865_set_ctrl_vflip(sensor, ctrl->val);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static const struct v4l2_ctrl_ops ov8865_ctrl_ops = {
	.g_volatile_ctrl = ov8865_g_volatile_ctrl,
	.s_ctrl = ov8865_s_ctrl,
};

static int ov8865_init_controls(struct ov8865_dev *sensor)
{
	const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops;
	struct ov8865_ctrls *ctrls = &sensor->ctrls;
	struct v4l2_ctrl_handler *hdl = &ctrls->handler;
	int ret;

	v4l2_ctrl_handler_init(hdl, 32);
	hdl->lock = &sensor->lock;
	ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
					  0, 0, link_freq_menu_items);
	if (ctrls->link_freq)
		ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
	ctrls->pixel_rate = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE,
					      0, INT_MAX, 1,
					      ov8865_calc_pixel_rate(sensor));
	ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 1,
					    2000, 1, 2000);
	ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, 1*16, 64*16 - 1,
					1, 64*16 - 1);
	ctrls->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
	ctrls->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
	if (hdl->error) {
		ret = hdl->error;
		goto err_free_ctrls;
	}

	ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;

	sensor->sd.ctrl_handler = hdl;

	return 0;

err_free_ctrls:
	v4l2_ctrl_handler_free(hdl);
	return ret;
}


static int ov8865_enum_frame_size(struct v4l2_subdev *sd,
				  struct v4l2_subdev_pad_config *cfg,
				  struct v4l2_subdev_frame_size_enum *fse)
{

	if (fse->pad != 0 || fse->index >= OV8865_NUM_MODES)
		return -EINVAL;

	fse->code = MEDIA_BUS_FMT_SBGGR10_1X10;
	fse->min_width = ov8865_mode_data[fse->index].hact;
	fse->max_width = fse->min_width;
	fse->min_height = ov8865_mode_data[fse->index].vact;
	fse->max_height = fse->min_height;

	return 0;
}

static int ov8865_enum_frame_interval(struct v4l2_subdev *sd,
				      struct v4l2_subdev_pad_config *cfg,
				      struct v4l2_subdev_frame_interval_enum
				      *fie)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	struct v4l2_fract tpf;
	int ret;

	if (fie->pad != 0 || fie->index >= OV8865_NUM_FRAMERATES)
		return -EINVAL;

	tpf.numerator = 1;
	tpf.denominator = ov8865_framerates[fie->index];

	ret = ov8865_try_frame_interval(sensor, &tpf,
					fie->width, fie->height);
	if (ret < 0)
		return -EINVAL;

	fie->interval = tpf;

	return 0;
}

static int ov8865_g_frame_interval(struct v4l2_subdev *sd,
				   struct v4l2_subdev_frame_interval *fi)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);

	mutex_lock(&sensor->lock);
	fi->interval = sensor->frame_interval;
	mutex_unlock(&sensor->lock);

	return 0;
}

static int ov8865_s_frame_interval(struct v4l2_subdev *sd,
				   struct v4l2_subdev_frame_interval *fi)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	const struct ov8865_mode_info *mode;
	int frame_rate, ret = 0;

	if (fi->pad != 0)
		return -EINVAL;

	mutex_lock(&sensor->lock);

	if (sensor->streaming) {
		ret = -EBUSY;
		goto out;
	}

	mode = sensor->current_mode;

	frame_rate = ov8865_try_frame_interval(sensor, &fi->interval,
					       mode->hact, mode->vact);
	if (frame_rate < 0) {
		fi->interval = sensor->frame_interval;
		goto out;
	}

	mode = ov8865_find_mode(sensor, frame_rate, mode->hact,
				 mode->vact, true);
	if (!mode) {
		ret = -EINVAL;
		goto out;
	}

	if (mode != sensor->current_mode ||
	    frame_rate != sensor->current_fr) {
		sensor->current_fr = frame_rate;
		sensor->frame_interval = fi->interval;
		sensor->current_mode = mode;

		__v4l2_ctrl_s_ctrl_int64(sensor->ctrls.pixel_rate,
					 ov8865_calc_pixel_rate(sensor));
	}

out:
	mutex_unlock(&sensor->lock);
	return ret;
}

static int ov8865_enum_mbus_code(struct v4l2_subdev *sd,
				 struct v4l2_subdev_pad_config *cfg,
				 struct v4l2_subdev_mbus_code_enum *code)
{
	if (code->pad != 0 || code->index >= ARRAY_SIZE(ov8865_formats))
		return -EINVAL;

	code->code = ov8865_formats[code->index].code;

	return 0;
}

static int ov8865_s_stream(struct v4l2_subdev *sd, int enable)
{
	struct ov8865_dev *sensor = to_ov8865_dev(sd);
	struct i2c_client *client = sensor->i2c_client;
	int ret = 0;

	/* call s_power() ourselves for regular PCs. s_power() handles
	 * mutex, so call it here.
	 */
	if (enable) {
		ret = ov8865_s_power(sd, true);
		if (ret) {
			dev_err(&client->dev, "s_power failed\n");
			ov8865_s_power(sd, false);
			goto out;
		}
	}

	mutex_lock(&sensor->lock);

	if (sensor->streaming == !enable) {
		ret = ov8865_write_reg(sensor, OV8865_SW_STANDBY_REG, enable ?
				     OV8865_SW_STANDBY_STANDBY_N : 0x00);
		if (ret)
			goto out;

		ret = ov8865_write_reg(sensor, OV8865_MIPI_CTRL_REG,
				       enable ? 0x72 : 0x62);
		if (ret)
			goto out;

		if (!ret)
			sensor->streaming = enable;
	}

out:
	mutex_unlock(&sensor->lock);

	/* call s_power() ourselves for regular PCs. s_power() handles
	 * mutex, so call it here.
	 */
	if (!enable)
		ov8865_s_power(sd, false);

	return ret;
}

static const struct v4l2_subdev_core_ops ov8865_core_ops = {
	.s_power = ov8865_s_power,
	.log_status = v4l2_ctrl_subdev_log_status,
	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};

static const struct v4l2_subdev_video_ops ov8865_video_ops = {
	.g_frame_interval = ov8865_g_frame_interval,
	.s_frame_interval = ov8865_s_frame_interval,
	.s_stream = ov8865_s_stream,
};

static const struct v4l2_subdev_pad_ops ov8865_pad_ops = {
	.enum_mbus_code = ov8865_enum_mbus_code,
	.get_fmt = ov8865_get_fmt,
	.set_fmt = ov8865_set_fmt,
	.enum_frame_size = ov8865_enum_frame_size,
	.enum_frame_interval = ov8865_enum_frame_interval,
};

static const struct v4l2_subdev_ops ov8865_subdev_ops = {
	.core = &ov8865_core_ops,
	.video = &ov8865_video_ops,
	.pad = &ov8865_pad_ops,
};

static int ov8865_get_regulators(struct ov8865_dev *sensor)
{
	int i;

	for (i = 0; i < OV8865_NUM_SUPPLIES; i++)
		sensor->supplies[i].supply = ov8865_supply_names[i];

	return devm_regulator_bulk_get(&sensor->i2c_client->dev,
				       OV8865_NUM_SUPPLIES,
				       sensor->supplies);
}

static int ov8865_check_chip_id(struct ov8865_dev *sensor)
{
	struct i2c_client *client = sensor->i2c_client;
	int ret = 0;
	u8 chip_id_0, chip_id_1, chip_id_2;
	u32 chip_id = 0x000000;

	ret = ov8865_set_power_on(sensor);
	if (ret)
		return ret;

	ret = ov8865_read_reg(sensor, OV8865_CHIP_ID_REG, &chip_id_0);
	if (ret) {
		dev_err(&client->dev, "%s: failed to reach chip identifier\n",
			__func__);
		goto power_off;
	}

	ret = ov8865_read_reg(sensor, OV8865_CHIP_ID_REG + 1, &chip_id_1);
	if (ret) {
		dev_err(&client->dev, "%s: failed to reach chip identifier\n",
			__func__);
		goto power_off;
	}

	ret = ov8865_read_reg(sensor, OV8865_CHIP_ID_REG + 2, &chip_id_2);
	if (ret) {
		dev_err(&client->dev, "%s: failed to reach chip identifier\n",
			__func__);
		goto power_off;
	}

	chip_id = ((u32)chip_id_0 << 16) | ((u32)chip_id_1 << 8) |
		((u32)chip_id_2);

	if (chip_id != OV8865_CHIP_ID) {
		dev_err(&client->dev, "%s: wrong chip identifier, expected 0x008865, got 0x%x\n", __func__, chip_id);
		ret = -ENXIO;
	}

	dev_info(&client->dev, "ov8865 detected at address 0x%02x\n", client->addr);

power_off:
	ov8865_set_power_off(sensor);
	return ret;
}

/* Get acpi_device of dependent INT3472 device */
static struct acpi_device *get_dep_adev(struct device *dev)
{
	struct acpi_handle *dev_handle;
	struct acpi_device *sensor_adev;
	struct acpi_handle_list dep_devices;
	struct acpi_device *dep_adev;
	acpi_status status;
	const char *dep_hid = "INT3472";
	int i;

	sensor_adev = acpi_dev_get_first_match_dev(OV8865_ACPI_HID, NULL, -1);
	if (!sensor_adev) {
		dev_err(dev, "Couldn't get sensor ACPI device\n");
		return ERR_PTR(-ENODEV);
	}
	dev_handle = sensor_adev->handle;
	acpi_dev_put(sensor_adev);

	if (!acpi_has_method(dev_handle, "_DEP")) {
		dev_err(dev, "No _DEP entry found\n");
		return ERR_PTR(-ENODEV);
	}

	status = acpi_evaluate_reference(dev_handle, "_DEP", NULL, &dep_devices);
	if (ACPI_FAILURE(status)) {
		dev_err(dev, "Failed to evaluate _DEP.\n");
		return ERR_PTR(-ENODEV);
	}

	for (i = 0; i < dep_devices.count; i++) {
		struct acpi_device_info *info;
		int match;

		status = acpi_get_object_info(dep_devices.handles[i], &info);
		if (ACPI_FAILURE(status)) {
			dev_dbg(dev,
				"Error reading _DEP device info, continue next\n");
			continue;
		}

		match = info->valid & ACPI_VALID_HID &&
			!strcmp(info->hardware_id.string, dep_hid);

		kfree(info);

		if (!match)
			continue;

		if (acpi_bus_get_device(dep_devices.handles[i], &dep_adev)) {
			dev_err(dev, "Error getting dependent ACPI device\n");
			return ERR_PTR(-ENODEV);
		}

		/* found acpi_device of dependent device */
		break;
	}

	if (!dep_adev) {
		dev_err(dev, "Dependent ACPI device not found\n");
		return ERR_PTR(-ENODEV);
	}

	dev_info(dev, "Dependent ACPI device found: %s\n",
		 dev_name(&dep_adev->dev));

	return dep_adev;
}

/* Get dependent INT3472 device */
static struct device *get_dep_dev(struct device *dev)
{
	struct acpi_device *dep_adev;
	struct acpi_device_physical_node *dep_phys;

	dep_adev = get_dep_adev(dev);
	if (!dep_adev) {
		dev_err(dev, "get_dep_adev() failed\n");
		return ERR_PTR(-ENODEV);
	}

	/*
	 * HACK: We know that the PMIC is a "discrete" PMIC, an ACPI device
	 * that just serves as a container to list system GPIOs.
	 *
	 * The ACPI device has no fwnode, nor does it have a platform device.
	 * This prevents fetching GPIOs. It however seems to be backed by the
	 * PCI root complex (pci0000:00/0000:00:00.0) as its physical device,
	 * and that device has its fwnode set to \_SB.PCI0.DSC1. Whether this
	 * is correct or not is unknown, let's just get the physical device and
	 * move on for now.
	 *
	 * (@kitakar5525)This is observed on Microsoft Surface Go series
	 * and Acer Switch Alpha 12.
	 */
	dep_phys = list_first_entry_or_null(&dep_adev->physical_node_list,
					    struct acpi_device_physical_node,
					    node);
	if (!dep_phys) {
		dev_info(dev,
			 "Error getting physical node of dependent device\n");
		return ERR_PTR(-ENODEV);
	}

	dev_info(dev, "Dependent device found: %s\n", dev_name(dep_phys->dev));

	return dep_phys->dev;
}

static int ov8865_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	struct fwnode_handle *endpoint;
	struct ov8865_dev *sensor;
	const struct ov8865_mode_info *default_mode;
	struct v4l2_mbus_framefmt *fmt;
	struct device *dep_dev;
	u32 rotation;
	int ret = 0;

	dev_info(&client->dev, "%s() called", __func__);

	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
	if (!sensor)
		return -ENOMEM;

	sensor->i2c_client = client;

	if (acpi_dev_present(OV8865_ACPI_HID, NULL, -1)) {
		dev_info(dev, "system is acpi-based\n");
		sensor->is_acpi_based = true;
	} else
		dev_info(dev, "system is not acpi-based\n");

	/*
	 * Default init sequence initialize sensor to
	 * RAW SBGGR10 3264x1836@30fps.
	 */

	default_mode = &ov8865_mode_data[OV8865_MODE_QUXGA_3264_2448];

	fmt = &sensor->fmt;
	fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10;
	fmt->colorspace = V4L2_COLORSPACE_RAW;
	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
	fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
	fmt->width = default_mode->hact;
	fmt->height = default_mode->vact;
	fmt->field = V4L2_FIELD_NONE;
	sensor->frame_interval.numerator = 1;
	sensor->frame_interval.denominator = ov8865_framerates[OV8865_30_FPS];
	sensor->current_fr = OV8865_30_FPS;
	sensor->current_mode = default_mode;
	sensor->last_mode = default_mode;

	if (!sensor->is_acpi_based) {
		/* Optional indication of physical rotation of sensor. */
		ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation",
						&rotation);
		if (!ret) {
			switch (rotation) {
			case 180:
				sensor->upside_down = true;
				/* fall through */
			case 0:
				break;
			default:
				dev_warn(dev, "%u degrees rotation is not supported, ignoring..\n",
					rotation);
			}
		}
	} else {
		/* TODO: read from fwnode or SSDB */
		rotation = 180;
		sensor->upside_down = true;
	}

	if (!sensor->is_acpi_based) {
		endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev),
							NULL);
		if (!endpoint) {
			dev_err(dev, "endpoint node not found\n");
			return -EINVAL;
		}

		ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
		fwnode_handle_put(endpoint);
		if (ret) {
			dev_err(dev, "Could not parse endpoint\n");
			return ret;
		}
	}

	/* For DT-based systems */
	if (!sensor->is_acpi_based) {
		/* Get system clock (xclk). */
		sensor->xclk = devm_clk_get(dev, "xclk");
		if (IS_ERR(sensor->xclk)) {
			dev_err(dev, "failed to get xclk\n");
			return PTR_ERR(sensor->xclk);
		}

		ret = clk_set_rate(sensor->xclk, OV8865_XCLK_FREQ);
		if (ret < 0) {
			dev_err(dev, "Failed to set xclk rate (24MHz)\n");
			return ret;
		}

		/* Request optional power down pin. */
		sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown",
							GPIOD_OUT_HIGH);
		if (IS_ERR(sensor->pwdn_gpio))
			return PTR_ERR(sensor->pwdn_gpio);

		/* Request optional reset pin. */
		sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset",
							GPIOD_OUT_HIGH);
		if (IS_ERR(sensor->reset_gpio))
			return PTR_ERR(sensor->reset_gpio);

		ret = ov8865_get_regulators(sensor);
		if (ret)
			return ret;
	}

	/* For ACPI-based systems */
	if (sensor->is_acpi_based) {
		sensor->dep_dev = get_dep_dev(&client->dev);
		if (IS_ERR(sensor->dep_dev)) {
			ret = PTR_ERR(sensor->dep_dev);
			dev_err(&client->dev, "cannot get dep_dev: ret %d\n", ret);
			return ret;
		}
		dep_dev = sensor->dep_dev;

		ret = gpio_crs_get(sensor, dep_dev);
		if (ret) {
			dev_err(dep_dev, "Failed to get _CRS GPIOs\n");
			return ret;
		}
	}

	v4l2_i2c_subdev_init(&sensor->sd, client, &ov8865_subdev_ops);
	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
			    V4L2_SUBDEV_FL_HAS_EVENTS;
	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
	if (ret)
		return ret;

	mutex_init(&sensor->lock);

	ret = ov8865_check_chip_id(sensor);
	if (ret)
		goto err_entity_cleanup;

	ret = ov8865_init_controls(sensor);
	if (ret)
		goto err_entity_cleanup;

	ret = v4l2_async_register_subdev(&sensor->sd);
	if (ret)
		goto err_free_ctrls;

	return 0;

err_free_ctrls:
	v4l2_ctrl_handler_free(&sensor->ctrls.handler);
err_entity_cleanup:
	mutex_destroy(&sensor->lock);
	media_entity_cleanup(&sensor->sd.entity);
	/* For ACPI-based systems */
	if (sensor->is_acpi_based)
		gpio_crs_put(sensor);
	return ret;
}


static int ov8865_remove(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct ov8865_dev *sensor = to_ov8865_dev(sd);

	dev_info(&client->dev, "%s() called", __func__);

	/* For ACPI-based systems */
	if (sensor->is_acpi_based)
		gpio_crs_put(sensor);

	v4l2_async_unregister_subdev(&sensor->sd);
	mutex_destroy(&sensor->lock);
	media_entity_cleanup(&sensor->sd.entity);
	v4l2_ctrl_handler_free(&sensor->ctrls.handler);

	return 0;
}

static const struct i2c_device_id ov8865_id[] = {
	{ "ov8865", 0 },
	{ },
};
MODULE_DEVICE_TABLE(i2c, ov8865_id);

static const struct of_device_id ov8865_dt_ids[] = {
	{ .compatible = "ovti,ov8865" },
	{ }
};
MODULE_DEVICE_TABLE(of, ov8865_dt_ids);

#ifdef CONFIG_ACPI
static const struct acpi_device_id ov8865_acpi_ids[] = {
	{OV8865_ACPI_HID},
	{},
};
MODULE_DEVICE_TABLE(acpi, ov8865_acpi_ids);
#endif

static struct i2c_driver ov8865_i2c_driver = {
	.driver	= {
		 .name = "ov8865",
		 .of_match_table = ov8865_dt_ids,
		 .acpi_match_table = ACPI_PTR(ov8865_acpi_ids),
	 },
	 .id_table     = ov8865_id,
	 .probe_new    = ov8865_probe,
	 .remove       = ov8865_remove,
};

module_i2c_driver(ov8865_i2c_driver);

MODULE_DESCRIPTION("OV8865 MIPI Camera Subdev Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kévin L'hôpital <kevin.lhopital@bootlin.com>");
